#!/bin/bash
# Copyright 2021 Henrik Grimler
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Script for adding new debs to local repository
#

# archive with debs to extract and sort
archive="$1"
# base directory containing one or several termux repos
mirror_base=/var/www/termux
# log file where errors and info from this script is saved
log_file="$HOME"/termuxsigner.log
# gpg key to use for signing repository
key_id=0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZ0123
# lockfile to prevent several instances of this script
lockfile="$HOME"/termux.lock
# if another instance is running, re-try to lock this many seconds
# before giving up
timeout=300

exec 2>> "$log_file"

archive_regex="([0-9a-z-]*)-packages.zip"

log_to_file() {
	echo "$(date "+%Y-%m-%d %H:%M:%S"): $@" | tee -a "$log_file"
}

###
# cd to $HOME, incron uses / as pwd
###
cd "$HOME" || exit 1

###
# Verify that an add-termux-deb process is not already running
###
touch "$lockfile"
exec {fd}<>"$lockfile"

# Try to lock file descriptor $fd during $timeout seconds.
# (released automatically on exit)
if ! flock -x -w $timeout $fd; then
	log_to_file "Error: Could not lock onto $lockfile to parse $archive"
	exit 1
fi

###
# Check which repo we should update
###
if [[ $archive =~ $archive_regex ]]; then
	repo=${BASH_REMATCH[1]}-packages-24
else
	log_to_file "Error: archive $archive does not match expected filename"
	exit 1
fi

###
# Get suite and components from config file
###
suite=$(grep "APT::FTPArchive::Release::Suite" ~/"$repo.conf" \
	| grep -Po '"([a-z]*)"' | tr -d '"')
components=$(grep "APT::FTPArchive::Release::Components" ~/"$repo.conf" \
	| grep -Po '"([a-z]*)"' | tr -d '"')

###
# Delete files from previous job and extract new archive
###
rm -f debs-arm.tar debs-aarch64.tar debs-i686.tar debs-x86_64.tar debs-all.tar
rm -f debs/*.deb debs/built_packages.txt debs/deleted_packages.txt
unzip -q -o "$archive"

###
# Match name, version and arch from deb name and mv to correct directory
###
arches_to_rebuild=()
for arch in arm aarch64 i686 x86_64 all; do
	cd "$HOME" || exit 1
	if [ ! -f "debs-$arch.tar" ]; then
		continue
	fi

	log_to_file "Processing debs-$arch.tar for $repo"
	tar -xf "debs-$arch.tar"

	if [ -f ./debs/built_packages.txt ]; then
		while read -r deb_name; do
			log_to_file "Adding ${deb_name} to $repo"
			if [ -f ./debs/"${deb_name}"_*_"$arch".deb ]; then
				deb=debs/"${deb_name}"_*_"$arch".deb
				###
				# mv deb to the local repository
				###
				mkdir -p "$mirror_base/$repo/$arch/"
				mv $deb "$mirror_base/$repo/$arch/"
				log_to_file "Added "$deb" to $repo/$arch"
				# Add $arch to arches to rebuild for if
				# not already present there
				if [[ ! " ${arches_to_rebuild[*]} " =~ " $arch " ]]; then
					arches_to_rebuild+=("$arch")
				fi
			elif [ -f ./debs/"${deb_name}"_*_all.deb ]; then
				deb=debs/"${deb_name}"_*_all.deb
				###
				# mv deb to the local repository
				###
				mkdir -p "$mirror_base/$repo/all/"
				mv $deb "$mirror_base/$repo/all"
				log_to_file "Added "$deb" to $repo/all"
				# Add $arch to arches to rebuild for if
				# not already present there
				if [[ ! " ${arches_to_rebuild[*]} " =~ " all " ]]; then
					arches_to_rebuild+=("all")
				fi
			else
				log_to_file "Error: package $deb_name not found in archive"
			fi
		done<./debs/built_packages.txt
	fi

	###
	# Check if there are any packages to delete
	###
	if [ -f ./debs/deleted_packages.txt ]; then
		while read -r pkg; do
			log_to_file "Deleting $pkg from $repo"
			found_file=false
			cd "$mirror_base/$repo" || exit 1
			for file in "$arch/${pkg}"_*_"$arch.deb"; do
				if [ -f "$file" ]; then
					found_file=true
					rm "$file"
					# Add $arch to arches to rebuild for if
					# not already present there
					if [[ ! "x${arches_to_rebuild[*]}x" =~ " $arch " ]]; then
						arches_to_rebuild+=("$arch")
					fi
				fi
			done
			for file in all/"${pkg}"_*_all.deb; do
				if [ -f "$file" ]; then
					found_file=true
					rm "$file"
					# Add all to arches to rebuild for if
					# not already present there
					if [[ ! " ${arches_to_rebuild[*]} " =~ " all " ]]; then
						arches_to_rebuild+=("all")
					fi
				fi
			done
			if [ "$found_file" == "false" ]; then
				log_to_file "Error: cannot find $pkg in $arch or all"
			fi
		done<./debs/deleted_packages.txt
	fi
done

###
# Re-build repository for changed arches in an atomic way
###

# This first stage might take a while, so first create .tmp files to
# decrease likelyhood of users updating in the meanwhile.
for arch in "${arches_to_rebuild[@]}"; do
	cd "$mirror_base/$repo" || exit 1

	mkdir -p dists/"$suite"/"$components"/binary-"$arch"
	apt-ftparchive packages "$arch" > dists/"$suite"/"$components"/binary-"$arch"/Packages.tmp
	bzip2 -kf dists/"$suite"/"$components"/binary-"$arch"/Packages.tmp
	gzip --force --keep --no-name dists/"$suite"/"$components"/binary-"$arch"/Packages.tmp
	log_to_file "Re-built dists/$suite/$components/binary-$arch in $repo"
done

# Now we replace the old files with the new
for arch in "${arches_to_rebuild[@]}"; do
	cd "$mirror_base/$repo" || exit 1

	mv dists/"$suite"/"$components"/binary-"$arch"/Packages{.tmp,}
	mv dists/"$suite"/"$components"/binary-"$arch"/Packages{.tmp,}.bz2
	mv dists/"$suite"/"$components"/binary-"$arch"/Packages{.tmp,}.gz
done

cd "$mirror_base/$repo" || exit 1

apt-ftparchive -c ~/"$repo.conf" release dists/"$suite" > dists/"$suite"/Release
gpg --yes -abs -u $key_id -o dists/"$suite"/Release.gpg dists/"$suite"/Release

log_to_file "Re-signed $repo"
